#include <assert.h>
#include <stdio.h>

#define AT_CMD_IMPLEMENTATION
// comment out the following #define if you want to see the debugging information
#define AT_OS(...)
#include "at_cmd.h"

static void prefix_function(void);
static void postfix_function(void);

#define TEST_INIT prefix_function()
#define TEST_FINI postfix_function()
#include "acutest.h"

#ifndef A
#define A(X) ((sizeof(X)/sizeof(X[0])))
#endif //!A

#ifndef UNUSED
#define UNUSED(X) ((void)(X))
#endif //!UNUSED

//******************************************************************************
// at_cmd error handler
//******************************************************************************

static int error_count;
static void my_error_handler(const char* f, int l, const char* msg)
{
    AT_OS("\nError in %s(%d): %s\n", f, l, msg);
    error_count++;
}

//******************************************************************************
// acutest pre/postfix
//******************************************************************************

static void prefix_function(void)
{
    error_count = 0;
    at_error_handler = my_error_handler;
}

static void postfix_function(void)
{
    AT_DeRegister();
}

//******************************************************************************
// command sets, handlers, and command lines
//******************************************************************************

//**************************************
// handler definitions
//**************************************

static bool no_param_handler(const char *arg)
{
    AT_OS("\narg = %s\n", arg);
    if (arg) { return false; }
    else     { return true;  }
}

static bool with_param_handler(const char *arg)
{
    AT_OS("\narg = %s\n", arg);
    if (arg) { return true;  }
    else     { return false; }
}

//**************************************
// command sets
//**************************************

AT_CMD_BEGIN_SET(set_no_param)
    { "NO_PARAM", no_param_handler, "test a function with no parameter" },
    AT_CMD_TERMINATOR,
AT_CMD_END_SET(set_no_param)

AT_CMD_BEGIN_SET(set_with_param)
    { "WITH_PARAM", with_param_handler, "test a function with a parameter" },
    AT_CMD_TERMINATOR,
AT_CMD_END_SET(set_with_param)

//**************************************
// trial command lines
//**************************************

const char *command_lines[] =
{
    "AT+HELP;+LASTERROR",       // valid base commands
    "THIS ISN'T A COMMAND!",    // invalid command line
    "ATINVALID",                // invalid command
    "AT+NONEXISTENT",           // non-existent command
    "AT+NO_PARAM",              // command with no parameter
    "AT+WITH_PARAM=25",         // command with a parameter: value 25
    "AT+WITH_PARAM=FOOBAR",     // command with a parameter: value FOOBAR
};

//******************************************************************************
// test cases
//******************************************************************************

static void test_register_deregister(void)
{
    AT_Register(AT_CMD_SET(set_no_param));
    TEST_CHECK(error_count == 0);
    TEST_CHECK(!at_last_error_message);

    error_count = 0;
    AT_DeRegister();
    TEST_CHECK(error_count == 0);
    TEST_CHECK(!at_last_error_message);
};

static void test_base_commands(void)
{
    AT_Register(AT_CMD_SET(set_with_param));
    TEST_CHECK(error_count == 0);
    TEST_CHECK(!at_last_error_message);

    TEST_CHECK(AT_ProcessCommandLine(command_lines[0]));

    error_count = 0;
    AT_DeRegister();
    TEST_CHECK(error_count == 0);
    TEST_CHECK(!at_last_error_message);
}

static void test_invalid_command_line(void)
{
    AT_Register(AT_CMD_SET(set_no_param));
    TEST_CHECK(error_count == 0);
    TEST_CHECK(!at_last_error_message);

    TEST_CHECK(!AT_ProcessCommandLine(command_lines[1]));
    TEST_CHECK(error_count == 1);
    TEST_CHECK(at_last_error_message);

    error_count = 0;
    AT_DeRegister();
    TEST_CHECK(error_count == 0);
    TEST_CHECK(!at_last_error_message);
}

static void test_invalid_command(void)
{
    AT_Register(AT_CMD_SET(set_with_param));
    TEST_CHECK(error_count == 0);
    TEST_CHECK(!at_last_error_message);

    TEST_CHECK(!AT_ProcessCommandLine(command_lines[2]));
    TEST_CHECK(error_count == 1);
    TEST_CHECK(at_last_error_message);

    error_count = 0;
    AT_DeRegister();
    TEST_CHECK(error_count == 0);
    TEST_CHECK(!at_last_error_message);
}

static void test_nonexistent_command(void)
{
    AT_Register(AT_CMD_SET(set_no_param));
    TEST_CHECK(error_count == 0);
    TEST_CHECK(!at_last_error_message);

    TEST_CHECK(!AT_ProcessCommandLine(command_lines[3]));
    TEST_CHECK(error_count == 1);
    TEST_CHECK(at_last_error_message);

    error_count = 0;
    AT_DeRegister();
    TEST_CHECK(error_count == 0);
    TEST_CHECK(!at_last_error_message);
}

static void test_not_registered(void)
{
    AT_Register(AT_CMD_SET(set_with_param));
    TEST_CHECK(error_count == 0);
    TEST_CHECK(!at_last_error_message);

    TEST_CHECK(!AT_ProcessCommandLine(command_lines[3]));
    TEST_CHECK(error_count == 1);
    TEST_CHECK(at_last_error_message);

    error_count = 0;
    AT_DeRegister();
    TEST_CHECK(error_count == 0);
    TEST_CHECK(!at_last_error_message);
}

static void test_command_with_no_parameter(void)
{
    AT_Register(AT_CMD_SET(set_no_param));
    TEST_CHECK(error_count == 0);
    TEST_CHECK(!at_last_error_message);

    TEST_CHECK(AT_ProcessCommandLine(command_lines[4]));
    TEST_CHECK(error_count == 0);
    TEST_CHECK(!at_last_error_message);

    error_count = 0;
    AT_DeRegister();
    TEST_CHECK(error_count == 0);
    TEST_CHECK(!at_last_error_message);
}

static void test_commands_with_parameter(void)
{
    AT_Register(AT_CMD_SET(set_with_param));
    TEST_CHECK(error_count == 0);
    TEST_CHECK(!at_last_error_message);

    TEST_CHECK(AT_ProcessCommandLine(command_lines[5]));
    TEST_CHECK(error_count == 0);
    TEST_CHECK(!at_last_error_message);
    TEST_CHECK(AT_ProcessCommandLine(command_lines[6]));
    TEST_CHECK(error_count == 0);
    TEST_CHECK(!at_last_error_message);

    error_count = 0;
    AT_DeRegister();
    TEST_CHECK(error_count == 0);
    TEST_CHECK(!at_last_error_message);
}

static void test_multiple_command_tables(void)
{
    AT_Register(AT_CMD_SET(set_no_param));
    TEST_CHECK(error_count == 0);
    TEST_CHECK(!at_last_error_message);
    AT_Register(AT_CMD_SET(set_with_param));
    TEST_CHECK(error_count == 0);
    TEST_CHECK(!at_last_error_message);

    TEST_CHECK(AT_ProcessCommandLine(command_lines[4]));
    TEST_CHECK(error_count == 0);
    TEST_CHECK(!at_last_error_message);
    TEST_CHECK(AT_ProcessCommandLine(command_lines[5]));
    TEST_CHECK(error_count == 0);
    TEST_CHECK(!at_last_error_message);
    TEST_CHECK(AT_ProcessCommandLine(command_lines[6]));
    TEST_CHECK(error_count == 0);
    TEST_CHECK(!at_last_error_message);

    error_count = 0;
    AT_DeRegister();
    TEST_CHECK(error_count == 0);
    TEST_CHECK(!at_last_error_message);
}

TEST_LIST =
{
    { "registration and deregistration", test_register_deregister       },
    { "base commands",                   test_base_commands             },
    { "invalid command line",            test_invalid_command_line      },
    { "invalid command",                 test_invalid_command           },
    { "nonexistent command",             test_nonexistent_command       },
    { "not registered",                  test_not_registered            },
    { "command with no parameter",       test_command_with_no_parameter },
    { "commands with parameter",         test_commands_with_parameter   },
    { "multiple command tables",         test_multiple_command_tables   },

    { NULL, NULL },
};
